#pragma once

#include <string.h>
#include <string>
#include <iostream>

#include "Util.h"

typedef char rchar;

void utf8cpy(wchar_t *dest, int dstLen, const char *utf8);
rchar* utf8dup(const wchar_t *input);

/// A reference to an immutable string buffer (though this object is mutable)
class StringRef
{
	/**
	 *	LITERAL
	 *		the string is a constant literal, the pointer may be shared, but should
	 *		never be released (const rchar*)
	 *
	 *	ALLOCATED
	 *		it uses a reference-counted buffer for storing the string (Buffer object)
	 *
	 *	STD_STRING
	 *		the string object is constructed from a std::wstring and should be buffered
	 *		when it's copied (it's a short-term object, only for quick and cheap argument
	 *		passing) - it is not used in this embedded version
	 *			
	 */
	enum StoreType { NULLREF, LITERAL, ALLOCATED, STD_STRING };

	/// A reference counted buffer used internally
	class Buffer
	{
		/// A pointer to the buffer itself
		const rchar *buffer;
		/// Reference count on this string buffer
		int refcount;
		/// Length of the string (without the terminating '\0')
		size_t length;

		void construct(const rchar *str, size_t len);
	public:
		/// Initializes the object by acquiring the pointer
		inline Buffer(const rchar *str, bool acquire): buffer(str), refcount(1), length(strlen(str))
		{
			if(!acquire)
				construct(str, strlen(str));
		}

		/// Initializes the object with the given string (the string is copied) and a refcount of 1
		inline Buffer(const rchar *str): refcount(1)
		{
			construct(str, strlen(str));
		}

		/// Initializes the object with the given string (the string is copied) and a refcount of 1.
		/// Accepts a pointer and a string length instead of expecting a '\0' terminated string
		inline Buffer(const rchar *str, int len): refcount(1)
		{
			construct(str, len);
		}

		/// Releases the attached buffer
		inline ~Buffer()
		{
			delete buffer;
		}

		/// Conversion to const rchar*
		inline operator const rchar*() { return buffer; }

		/// Return the buffer
		inline const rchar* getBuffer() { return buffer; }

		/// Increments the reference counter
		inline void addRef() { ++refcount; }

		/// Decrements the reference counter and optionally releases the object
		inline void release() { --refcount; if(refcount <= 0) delete this; }

		inline size_t getLength() { return length; }
	};


	/// The storage type for this object
	StoreType type;
	/// Either only the buffer pointer or the buffer object reference is used
	union
	{
		/// The string buffer pointer
		const rchar *data;
		/// The buffer object reference
		Buffer *buffer;
	};

	void copyFrom(const StringRef &s);

	void release();

	inline StringRef(Buffer *buf): type(ALLOCATED), buffer(buf) { }
public:
	typedef rchar value_type;
	enum { npos = -1 };

	inline StringRef(): type(NULLREF), data(0) { }

	/// Constructs a string from a std::wstring
	inline StringRef(const std::string &s, bool allocNewBuffer=true)
	{
		if(allocNewBuffer)
		{
			type = ALLOCATED;
			buffer = new Buffer(s.c_str());
		} else
		{
			type = STD_STRING;
			data = s.c_str();
		}

	}

	/// Constructs a string from a string literal (immutable constant buffer - this is the default)
	///	or a mutable string buffer
	inline StringRef(const rchar *s, bool mutableBuffer=false)
	{
		if(s)
		{
			if(mutableBuffer)
			{
				type = ALLOCATED;
				buffer = new Buffer(s);
			} else
			{
				type = LITERAL;
				data = s;
			}
		} else
		{
			type = NULLREF;
			buffer = 0;
		}
	}

	inline StringRef(const rchar *s, int len)
	{
		type = ALLOCATED;
		buffer = new Buffer(s, len);
	}

	/// Constructs a string from another
	inline StringRef(const StringRef &other)
	{
		copyFrom(other);
	}

	inline const StringRef& operator=(const StringRef& other)
	{
		release();
		copyFrom(other);
		return *this;
	}

	inline bool operator==(const rchar* other) const
	{
		return *this == StringRef(other);
	}

	inline bool operator==(const StringRef& other) const
	{
		return getBuffer() == other.getBuffer() || type != NULLREF && other.type != NULLREF &&
			strcmp(getBuffer(), other.getBuffer())==0;
	}

	inline bool operator<(const StringRef& other) const
	{
		return getBuffer() != other.getBuffer() && type != NULLREF && other.type != NULLREF &&
			strcmp(getBuffer(), other.getBuffer())<0;
	}

	inline bool operator>(const StringRef& other) const
	{
		return getBuffer() != other.getBuffer() && type != NULLREF && other.type != NULLREF &&
			strcmp(getBuffer(), other.getBuffer())>0;
	}

	inline bool operator<=(const StringRef& other) const { return !(*this > other); }
	inline bool operator>=(const StringRef& other) const { return !(*this < other); }

	/// Releases the buffer if needed
	inline ~StringRef()
	{
		release();
	}

	/// Constructs a string from a C-style mutable string buffer (copies it)
	inline static StringRef fromMutable(const rchar *s)
	{
		return StringRef(s, true);
	}

	/// Constructs a string from a preallocated buffer. The buffer will be released with the delete operator.
	inline static StringRef acquire(const rchar *s)
	{
		return StringRef(new Buffer(s, true));
	}

        /// Constructs a string from a C-style mutable string buffer (copies it)
        inline static StringRef fromMutable(const rchar *s, int length)
        {
                AutoDeleteArray<rchar> buf = new rchar[length+1];
                memcpy(buf.getPointer(), s, length*sizeof(rchar));
                buf[length] = 0;
                return acquire(buf.dontDelete());
        }

	inline const rchar* getBuffer() const { if(type == ALLOCATED) return *buffer; else return data; }

	inline const rchar* operator*() const { return getBuffer(); }

	inline bool operator!() const { return type == NULLREF; }

	inline rchar operator[](int index) const { return getBuffer()[index]; }

	//inline operator const rchar *() const { return getBuffer(); }

	inline int find(rchar c) const
	{
		const rchar *buf = getBuffer();
		if(buf)
		{
			const rchar *res = strchr(buf, c);
			return res ? res-buf : npos;
		} else
			return npos;
	}

	inline bool startsWith(const StringRef &other)
	{
		if(other.type == NULLREF)
			return type == NULLREF;
		if(type == NULLREF)
			return false;

		// neither this or the other is NULLREF
		if(other.getLength() > getLength())
			return false;
		if(other.getLength() == getLength())
			return *this == other;
		return strncmp(other.getBuffer(), getBuffer(), other.getLength()) == 0;
	}

	inline bool endsWith(const StringRef &other)
	{
		if(other.type == NULLREF)
			return type == NULLREF;
		if(type == NULLREF)
			return false;

		// neither this or the other is NULLREF
		if(other.getLength() > getLength())
			return false;
		if(other.getLength() == getLength())
			return *this == other;
		return strncmp(other.getBuffer(),
			getBuffer()+getLength()-other.getLength(),
			other.getLength()) == 0;
	}

	inline int getLength() const
	{
		if(type == ALLOCATED) return (int)buffer->getLength();
		if(type == NULLREF || !data) return 0;
		return (int)strlen(data);
	}

	inline StringRef substr(int start, int length=-1) const
	{
		if(NULLREF)
			return StringRef();
		if(length < 0)
			length = getLength()-start;
		return StringRef(getBuffer()+start, length);
	}

	inline StringRef substring(int start) const
	{
		return substr(start);
	}

	inline StringRef substring(int start, int end) const
	{
		int length = end-start;
		return substr(start, length);
	}

	/**
	 * Converts the string to int. Does not return error code, but has a defaultValue
	 * parameter which will be returned if the string is not a number.
	 */
	int toInt(int defaultValue) const;

	/**
	 * Converts the string to float. Does not return error code, but has a defaultValue
	 * parameter which will be returned if the string is not a number.
	 */
	float toFloat(float defaultValue) const;
};

extern const StringRef NULL_STRING;

inline std::wostream& operator<<(std::wostream &stream, const StringRef &ref)
{
	stream << ref.getBuffer();
	return stream;
}

template<typename T> class CharBuffer
{
private:
	AutoDeleteArray<T> str;
	int length;
	int size;
public:
	enum { ALLOC_BY = 64 };
	inline CharBuffer(): length(0), size(0) { }
	inline void pushBack(T ch)
	{
		if(length >= size)
		{
			AutoDeleteArray<T> newStr = new T[size+ALLOC_BY];
			memcpy(newStr.getPointer(), str.getPointer(), size*sizeof(T));
			size += ALLOC_BY;
			str = newStr.dontDelete();
		}

		str[length++] = ch;
	}

	inline int getLength() { return length; }
	inline T* getBufferAndReset() { T *result = str.dontDelete(); length = 0; size = 0; return result; }
};

typedef CharBuffer<rchar> RCharBuffer;
